package nets

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

// HWebHttpReq 请求
type HWebHttpReq struct {
	*http.Request               // 请求
	timeout       time.Duration // 超时时间
	sslEnabled    bool          // 是否启用SSL
	tlsConfig     *tls.Config   // tls配置
	compressed    bool          // 是否启用压缩
	proxy         *url.URL      // 代理
	bodySize      int           // 限制请求体大小
}

// NewWebHttpReq 创建请求
func NewWebHttpReq() *HWebHttpReq {
	return &HWebHttpReq{
		Request: &http.Request{
			Header:     make(http.Header),
			Proto:      "HTTP/1.1",
			ProtoMajor: 1,
			ProtoMinor: 1,
		},
	}
}

// SetHeader 设置请求头
func (h *HWebHttpReq) SetHeader(key,
	value string) *HWebHttpReq {
	h.Header.Set(key, value)
	return h
}

// SetBody 设置请求体
func (h *HWebHttpReq) SetBody(body any) *HWebHttpReq {
	var bodyReader *bytes.Reader
	switch v := body.(type) {
	case io.Reader:
		tmpBuffer := bytes.NewBuffer(nil)
		_, err := io.Copy(tmpBuffer, v)
		if err != nil {
			panic(err)
		}
		h.Body = io.NopCloser(tmpBuffer)
		h.ContentLength = int64(tmpBuffer.Len())
	case []byte:
		bodyReader = bytes.NewReader(v)
		h.Body = io.NopCloser(bodyReader)
		h.ContentLength = int64(bodyReader.Len())
	case map[string]interface{}:
		tmpValues := url.Values{}
		for k := range v {
			tmpValues.Set(k, fmt.Sprintf("%v", v[k]))
		}
		bodyReader = bytes.NewReader([]byte(tmpValues.Encode()))
		h.Body = io.NopCloser(bodyReader)
		h.ContentLength = int64(bodyReader.Len())
	case url.Values:
		bodyReader = bytes.NewReader([]byte(v.Encode()))
		h.Body = io.NopCloser(bodyReader)
		h.ContentLength = int64(bodyReader.Len())
	case string:
		bodyReader = bytes.NewReader([]byte(v))
		h.Body = io.NopCloser(bodyReader)
		h.ContentLength = int64(bodyReader.Len())
	}
	return h
}

// SetMethod 设置请求方法
func (h *HWebHttpReq) SetMethod(method string) *HWebHttpReq {
	h.Method = method
	return h
}

// SetUrl 设置请求地址
func (h *HWebHttpReq) SetUrl(baseUrl string) *HWebHttpReq {
	h.URL, _ = url.Parse(baseUrl)
	return h
}

// SetUrlWithParams 设置请求地址
func (h *HWebHttpReq) SetUrlWithParams(params map[string]interface{}) *HWebHttpReq {
	if len(params) <= 0 {
		return h
	}
	tmpValues := make(url.Values)
	for k := range params {
		tmpValues.Set(k, fmt.Sprintf("%s", params[k]))
	}

	baseUrl := h.URL.String()
	if !strings.Contains(h.URL.String(), "?") {
		baseUrl = fmt.Sprintf("%s?%s", baseUrl, tmpValues.Encode())
	} else {
		baseUrl = fmt.Sprintf("%s&%s", baseUrl, tmpValues.Encode())
	}
	h.URL, _ = url.Parse(baseUrl)
	return h
}

// SetContext 设置请求上下文
func (h *HWebHttpReq) SetContext(ctx context.Context) *HWebHttpReq {
	h.WithContext(ctx)
	return h
}

// SetForm 设置表单请求参数
func (h *HWebHttpReq) SetForm(form map[string]interface{}) *HWebHttpReq {
	if len(form) <= 0 {
		return h
	}
	tmpValues := make(url.Values)
	for k := range form {
		tmpValues.Set(k, fmt.Sprintf("%s", form[k]))
	}
	h.SetHeader("Content-Type", "application/x-www-form-urlencoded")
	h.SetBody(tmpValues)
	return h
}

// SetProxy 设置代理
func (h *HWebHttpReq) SetProxy(proxy *url.URL) *HWebHttpReq {
	h.proxy = proxy
	return h
}

// ConvertMapToQueryString 将map转换为queryString
func (h *HWebHttpReq) ConvertMapToQueryString(param map[string]any) string {
	if param == nil {
		return ""
	}
	keys := make([]string, 0, len(param))
	for key := range param {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	var build strings.Builder
	for i, v := range keys {
		build.WriteString(v)
		build.WriteString("=")
		build.WriteString(fmt.Sprintf("%v", param[v]))
		if i != len(keys)-1 {
			build.WriteString("&")
		}
	}
	return build.String()
}

// StructToUrlValues 将结构体转换为url.Values
func (h *HWebHttpReq) StructToUrlValues(targetStruct any) (url.Values,
	error) {
	result := url.Values{}

	var m map[string]interface{}

	jsonBytes, err := json.Marshal(targetStruct)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(jsonBytes, &m)
	if err != nil {
		return nil, err
	}

	for k, v := range m {
		result.Add(k, fmt.Sprintf("%v", v))
	}

	return result, nil
}

// SetTls 设置tls
func (h *HWebHttpReq) SetTls(tlsConf *tls.Config) *HWebHttpReq {
	h.sslEnabled = true
	h.tlsConfig = tlsConf
	return h
}

// SetBodySize 设置请求体大小
func (h *HWebHttpReq) SetBodySize(size int) {
	h.bodySize = size
}

// SetCompressed 设置是否启用压缩
func (h *HWebHttpReq) SetCompressed(compressed bool) {
	h.compressed = compressed
}

// ParseResponseToJsonObj 解析响应为json对象
func (h *HWebHttpReq) ParseResponseToJsonObj(resp *http.Response,
	targetObj any) error {
	if resp == nil {
		return errors.New("resp is nil")
	}
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)
	return json.NewDecoder(resp.Body).Decode(targetObj)
}

// ParseResponseToString 解析响应为字符串
func (h *HWebHttpReq) ParseResponseToString(resp *http.Response) string {
	if resp == nil {
		return ""
	}
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)
	rsp, e := io.ReadAll(resp.Body)
	if e != nil {
		return ""
	}
	return string(rsp)
}

// Send 发送请求
func (h *HWebHttpReq) Send() (*http.Response,
	error) {
	client := &http.Client{
		Timeout: h.timeout,
		Transport: &http.Transport{
			DisableCompression:    !h.compressed,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
			DialContext: func(ctx context.Context, network, addr string) (net.Conn,
				error) {
				dialer := &net.Dialer{
					Timeout:   30 * time.Second,
					KeepAlive: 30 * time.Second,
				}
				return dialer.DialContext(ctx, network, addr)
			},
			MaxConnsPerHost:     3000,
			MaxIdleConnsPerHost: 1000,
			IdleConnTimeout:     90 * time.Second,
		},
	}
	// 是否启用SSL
	if h.sslEnabled {
		client.Transport.(*http.Transport).TLSClientConfig = h.tlsConfig
	}
	// 启用代理
	if h.proxy != nil {
		client.Transport.(*http.Transport).Proxy = http.ProxyURL(h.proxy)
	} else {
		client.Transport.(*http.Transport).Proxy = http.ProxyFromEnvironment
	}
	return client.Do(h.Request)
}

// AsyncSend 异步发送
func (h *HWebHttpReq) AsyncSend(respChan chan *http.Response, errChan chan error) {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				errChan <- fmt.Errorf("%v", err)
			}
		}()
		resp, err := h.Send()
		if err != nil {
			errChan <- err
		}
		respChan <- resp
	}()
}
